#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <sys/mman.h>
#include <semaphore.h>

#include "posix_ipc.h"

int ipc_pipe(int argc, char *argv[]){
    if(argc < 2){
        printf("Usage:  %s <string to child>\n", argv[0]);
        return -1;
    }

    int pipefd[2];
    if(pipe(pipefd) != 0){
        perror("pipe");
        return -1;
    }

    pid_t pid = fork();
    switch (pid){
        case -1:
            perror("fork");
            return -1;
        
        case 0:/*child process*/
            close(pipefd[1]);

            char buff[1024];
            ssize_t size_ch = read(pipefd[0], buff, sizeof(buff) - 1);
            if(size_ch < 0){
                perror("read");
                return -1;
            }
            buff[size_ch] = '\0';

            printf("read from parent:%s\n",buff);
            
            close(pipefd[0]);
            return 0;
        
        default:/*parent process*/
            close(pipefd[0]);

            ssize_t size_pa = write(pipefd[1], argv[1], strlen(argv[1]));
            if(size_pa < 0){
                perror("write");
                return -1;
            }

            wait(NULL);
            close(pipefd[1]);
            return 0;
    }
}

int ipc_popen(int argc, char *argv[]){
    if(argc < 2 || argv[1] == NULL){
        printf("Usage:%s <command>\n",argv[0]);
        return -1;
    }

    FILE *fd = popen(argv[1],"r");
    if(!fd){
        perror("popen");
        return -1;
    }

    char buf[1024];
    while(fgets(buf, sizeof(buf), fd) != NULL){
       printf("[%s]:%s\n", argv[0],buf);
    }

    pclose(fd);

    return 0;
}

int ipc_fifo_write(int argc, char *argv[]){
    if(argc < 3 || argv[1] == NULL || argv[2] == NULL){
        printf("Usage:%s <fifo-name> <string to write>\n",argv[0]);
        return -1;
    }

    if (mkfifo(argv[1], 0666) == -1) {
        perror("mkfifo");
    }

    int fd = open(argv[1], O_WRONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    write(fd, argv[2], strlen(argv[2]));
    printf("Writer sent: %s\n", argv[2]);

    close(fd);
    return 0;
}

int ipc_fifo_read(int argc, char *argv[]){
    if(argc < 2 || argv[1] == NULL){
        printf("Usage:%s <fifo-name> \n",argv[0]);
        return -1;
    }
    
    int fd = open(argv[1], O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    char buf[1024];
    ssize_t n = read(fd, buf, sizeof(buf)-1);
    if (n == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }
    buf[n] = '\0';
    printf("Reader received: %s\n", buf);

    close(fd);
    unlink(argv[1]);  /* delete fifo*/
    return 0;
}

int ipc_mqueue_send(int argc, char*argv[]){
    if(argc < 4 || argv[1] == NULL || argv[2] == NULL){
        printf("Usage:%s <mq-name> <string to write> <string >\n",argv[0]);
        return -1;
    }

    int prio = atoi(argv[3]);
    if(prio < 0){
        printf("invalid priority\n");
        return -1;
    }

    struct mq_attr attr;
    attr.mq_maxmsg = 10;  
    attr.mq_msgsize = 1024;

    mqd_t mq = mq_open(argv[1], O_CREAT | O_WRONLY, 0666, &attr);
    if (mq == (mqd_t)-1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }


    if (mq_send(mq, argv[2], strlen(argv[2]), prio) == -1) {  
        perror("mq_send");
        exit(EXIT_FAILURE);
    }

    mq_close(mq);  
    return 0;
}

int ipc_mqueue_recive(int argc, char *argv[]){
    if(argc < 2 || argv[1] == NULL){
        printf("Usage:%s <mq-name> \n",argv[0]);
        return -1;
    }

    mqd_t mq = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
    if (mq == (mqd_t)-1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    char buf[1024];
    unsigned int prio;
    ssize_t n;
    while(1){
        memset(buf, 0, sizeof(buf));
        n = mq_receive(mq, buf, sizeof(buf), &prio);
        if(n <= 0)
            break;
        buf[n] = '\0';
        printf("Received: %s (priority: %u)\n", buf, prio);
    }
    
    if(n < 0)
        perror("mq_receive");

    mq_close(mq);        
    mq_unlink(argv[1]);
    return 0;
}

int ipc_shm_write(int argc, char *argv[]){
    if(argc < 3 || argv[1] == NULL || argv[2] == NULL){
        printf("Usage:%s <shm-name> <string to write>\n",argv[0]);
        return -1;
    }

    int shm_fd = shm_open(argv[1], O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        return -1;
    }

    if (ftruncate(shm_fd, SHM_SIZE) == -1) {
        perror("ftruncate");
    close(shm_fd);
        return -1;
    }

    char *shm_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (shm_ptr == MAP_FAILED) {
        perror("mmap");
        close(shm_fd);
        return -1;
    }

    sem_t *sem = sem_open(argv[1], O_CREAT, 0666, 0);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        munmap(shm_ptr, SHM_SIZE);
        close(shm_fd);
        return -1;
    }

    strcpy(shm_ptr, argv[2]);
    printf("Writer wrote: %s\n", shm_ptr);

    if (sem_post(sem) == -1) {
        perror("sem_post");
    }

    munmap(shm_ptr, SHM_SIZE);
    close(shm_fd);
    sem_close(sem);

    return 0;
}

int ipc_shm_read(int argc, char*argv[]){
    if(argc < 2 || argv[1] == NULL){
        printf("Usage:%s <shm-name> \n",argv[0]);
        return -1;
    }

    int shm_fd = shm_open(argv[1], O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        return -1;
    }

    char *shm_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (shm_ptr == MAP_FAILED) {
        perror("mmap");
        close(shm_fd);
        return -1;
    }

    sem_t *sem = sem_open(argv[1], O_RDWR);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        munmap(shm_ptr, SHM_SIZE);
        close(shm_fd);
        return -1;
    }

    if (sem_wait(sem) == -1) {
        perror("sem_wait");
    }

    printf("Reader read: %s\n", shm_ptr);

    munmap(shm_ptr, SHM_SIZE);
    close(shm_fd);
    sem_close(sem);
    shm_unlink(argv[1]);
    sem_unlink(argv[1]);

    return 0;
}
